package com.zb.zeus.common.security.aspect;

import com.google.common.base.Stopwatch;
import com.zb.zeus.common.core.exception.PreAuthorizeException;
import com.zb.zeus.common.core.utils.PerfLogger;
import com.zb.zeus.common.core.utils.StringUtils;
import com.zb.zeus.common.security.annotation.PreAuthorize;
import com.zb.zeus.common.security.context.UserContextHolder;
import com.zb.zeus.common.security.service.TokenHolderService;
import com.zb.zeus.system.api.model.LoginUser;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 自定义权限实现
 *
 * @author zb.zeus
 */
@Aspect
@Component
public class PreAuthorizeAspect {
    @Autowired
    private TokenHolderService tokenHolderService;

    /**
     * 所有权限标识
     */
    private static final String ALL_PERMISSION = "*:*:*";

    /**
     * 管理员角色权限标识
     */
    private static final String SUPER_ADMIN = "admin";

    /**
     * 数组为0时
     */
    private static final Integer ARRAY_EMPTY = 0;

    //@Around("@annotation(com.zb.zeus.common.security.annotation.PreAuthorize)")
    @Around("execution(* com.zb..*.controller..*Controller.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        Signature signature = point.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        //String methodSignatureName = methodSignature.getName();

        Stopwatch stopwatch = Stopwatch.createStarted();
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        boolean hasError = false;
        LoginUser loginUser = UserContextHolder.DEFAULT_USER;

        try {
            LoginUser tmpUser = tokenHolderService.getLoginUser();
            UserContextHolder.setLoginUser(Objects.isNull(tmpUser) ? UserContextHolder.DEFAULT_USER : tmpUser);
            loginUser = UserContextHolder.getLoginUser();
            PreAuthorize annotation = method.getAnnotation(PreAuthorize.class);
            if (annotation == null) {
                return point.proceed();
            }

            if (StringUtils.isNotEmpty(annotation.hasPermi())) {
                if (!hasPermi(annotation.hasPermi())) {
                    throw new PreAuthorizeException(String.format("当前用户无%s权限", annotation.hasPermi()));
                }
            } else if (StringUtils.isNotEmpty(annotation.lacksPermi())) {
                if (!lacksPermi(annotation.lacksPermi())) {
                    throw new PreAuthorizeException();
                }
            } else if (ARRAY_EMPTY < annotation.hasAnyPermi().length) {
                if (!hasAnyPermi(annotation.hasAnyPermi())) {
                    throw new PreAuthorizeException();
                }
            } else if (StringUtils.isNotEmpty(annotation.hasRole())) {
                if (!hasRole(annotation.hasRole())) {
                    throw new PreAuthorizeException();
                }
            } else if (StringUtils.isNotEmpty(annotation.lacksRole())) {
                if (!lacksRole(annotation.lacksRole())) {
                    throw new PreAuthorizeException();
                }
            } else if (ARRAY_EMPTY < annotation.hasAnyRoles().length) {
                if (!hasAnyRoles(annotation.hasAnyRoles())) {
                    throw new PreAuthorizeException();
                }
            }

            return point.proceed();
        } catch (Throwable throwable) {
            hasError = true;
            throw throwable;
        } finally {
            UserContextHolder.remove();
            stopwatch.stop();
            handleRequestPerfLog(request, stopwatch.elapsed(TimeUnit.MILLISECONDS), hasError, loginUser);

        }
    }

    /**
     * 验证用户是否具备某权限
     *
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(String permission) {
        //LoginUser userInfo = tokenService.getLoginUser();
        LoginUser userInfo = UserContextHolder.getLoginUser();
        if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getPermissions())) {
            return false;
        }
        return hasPermissions(userInfo.getPermissions(), permission);
    }

    /**
     * 验证用户是否不具备某权限，与 hasPermi逻辑相反
     *
     * @param permission 权限字符串
     * @return 用户是否不具备某权限
     */
    public boolean lacksPermi(String permission) {
        return hasPermi(permission) != true;
    }

    /**
     * 验证用户是否具有以下任意一个权限
     *
     * @param permissions 权限列表
     * @return 用户是否具有以下任意一个权限
     */
    public boolean hasAnyPermi(String[] permissions) {
        //LoginUser userInfo = tokenService.getLoginUser();
        LoginUser userInfo = UserContextHolder.getLoginUser();
        if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getPermissions())) {
            return false;
        }
        Collection<String> authorities = userInfo.getPermissions();
        for (String permission : permissions) {
            if (permission != null && hasPermissions(authorities, permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户是否拥有某个角色
     *
     * @param role 角色字符串
     * @return 用户是否具备某角色
     */
    public boolean hasRole(String role) {
        //LoginUser userInfo = tokenService.getLoginUser();
        LoginUser userInfo = UserContextHolder.getLoginUser();
        ;
        if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getRoles())) {
            return false;
        }
        for (String roleKey : userInfo.getRoles()) {
            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(role)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证用户是否不具备某角色，与 isRole逻辑相反。
     *
     * @param role 角色名称
     * @return 用户是否不具备某角色
     */
    public boolean lacksRole(String role) {
        return hasRole(role) != true;
    }

    /**
     * 验证用户是否具有以下任意一个角色
     *
     * @param roles 角色列表
     * @return 用户是否具有以下任意一个角色
     */
    public boolean hasAnyRoles(String[] roles) {
        //LoginUser userInfo = tokenService.getLoginUser();
        LoginUser userInfo = UserContextHolder.getLoginUser();
        if (StringUtils.isNull(userInfo) || CollectionUtils.isEmpty(userInfo.getRoles())) {
            return false;
        }
        for (String role : roles) {
            if (hasRole(role)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含权限
     *
     * @param authorities 权限列表
     * @param permission  权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Collection<String> authorities, String permission) {
        return authorities.stream().filter(StringUtils::hasText)
                .anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
    }

    private void handleRequestPerfLog(HttpServletRequest request, long costMillis, boolean hasError, LoginUser loginUser) {
        String reqUri = request.getRequestURI();
        String method = request.getMethod();
        String remoteAddr = getRemoteIp(request);
        if (hasError) {
            PerfLogger.error("{}|{}|{}|{}|{}ms", loginUser.getAdNum(), reqUri, method, remoteAddr, costMillis);
        } else {
            PerfLogger.info("{}|{}|{}|{}|{}ms", loginUser.getAdNum(), reqUri, method, remoteAddr, costMillis);
        }
    }

    public static String getRemoteIp(HttpServletRequest request) {
        String ip = null;
        String unknown = "unknown";
        //X-Forwarded-For：Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ipAddresses) || StringUtils.equalsIgnoreCase(ipAddresses, unknown)) {
            //Proxy-Client-IP：apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || StringUtils.equalsIgnoreCase(ipAddresses, unknown)) {
            //WL-Proxy-Client-IP：weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || StringUtils.equalsIgnoreCase(ipAddresses, unknown)) {
            //HTTP_CLIENT_IP：有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (StringUtils.isEmpty(ipAddresses) || StringUtils.equalsIgnoreCase(ipAddresses, unknown)) {
            //X-Real-IP：nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }
        //有些网络通过多层代理，那么获取到的ip就会有多个，一般都是通过逗号（,）分割开来，并且第一个ip为客户端的真实IP
        if (StringUtils.isNotEmpty(ipAddresses)) {
            ip = ipAddresses.split(",")[0];
        }

        //还是不能获取到，最后再通过request.getRemoteAddr();获取
        if (StringUtils.isEmpty(ip) || StringUtils.equalsIgnoreCase(ipAddresses, unknown)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
